項目20 変数の型がどのように決まるか理解する
TypeScriptによる型の拡大
TypeScriptがコードをチェックする静的解析のタイミングでは、変数は型を持つ。もし、型を指定していない場合、型チェッカーは型を決定しないといけない
つまり、型チェッカーは指定された単一の値から取りうる値の集合を決める必要がある
これを型の拡大 (widening)という
型の拡大が型エラーを引き起こすことがある
下記のコードは型の拡大によって、関数の引数の型と型不一致が発生する例
code:ts
let x = 'x';
let vec = {x: 10, y: 20, z: 30};
getComponent(vec, x);
// ~ Argument of type 'string' is not assignable
// to parameter of type '"x" | "y" | "z"'
// 型 'string' の引数を型 '"x" | "y" | "z"' の
// パラメーターに割り当てることはできません。
letで宣言された変数にプリミティブ値を代入すると、その「基本型」に拡大されるのが一般的
型の拡大プロセスを制御する
constを使ってより狭い型を与えさせる
code:ts
const x = 'x';
// ^? const x: "x"
let vec = {x: 10, y: 20, z: 30};
getComponent(vec, x); // OK
明示的に型アノテーションを指定する
TypeScriptはオブジェクトの型を推論する際に、letで宣言されているかのように扱う。
なので、特にオブジェクトに対して具体的な型を指定したい場合に型アノテーションは有効
code:ts
const obj: { x: string | number } = { x: 1 };
// ^? const obj: { x: string | number; }
constアサーションを使う
値の後にas constを書くと、TypeScriptはその値について可能な限り狭い型を推論する
code:ts
const obj1 = { x: 1, y: 2 };// ^? const obj1: { x: number; y: number; }
const obj2 = { x: 1 as const, y: 2 };// ^? const obj2: { x: 1; y: number; }
const obj3 = { x: 1, y: 2 } as const;// ^? const obj3: { readonly x: 1; readonly y: 2; }
配列でas constを使用するとタプル型が推論される
code:ts
const arr1 = 1, 2, 3;// ^? const arr1: number[] その他のTips
配列型ではなく、タプル型で推論させつつ、タプル内の各要素の型は基本型のまま拡大させる
code:ts
function tuple<T extends unknown[]>(...elements: T) { return elements; }
Object.freezeを使った型推論
Object.freezeは推論された型にreadonly修飾子を付与する
浅いreadonlyであることに注意
code:ts
const frozenArray = Object.freeze(1, 2, 3); // ^? const frozenArray: readonly number[]
const frozenObj = Object.freeze({x: 1, y: 2});
// ^? const frozenObj: Readonly<{ x: 1; y: 2; }>
satisfies演算子を使う
値がある方の要件を満たすことを保証し、TypeScriptがそれより広い型を推論しないようガイドする役割
satisfiesを使うことで、値の型がPointを超えて推論されることを防ぐ例
code:ts
// ^? const capitals1: { ny: number[]; ca: number[]; }
const capitals2 = {
} satisfies Record<string, Point>;
capitals2